1
Project Overview
Project Goal
Build a "ShopMart" e-commerce app that displays products, allows users to browse products, view details, add items to cart, manage cart items, and complete a checkout process.
Features to Implement
- Product listing with categories
- Product detail view
- Shopping cart with add/remove/update quantities
- Cart total calculation
- Checkout screen
- Order confirmation
- Search functionality
2
Project Setup
Step 1: Create Project
flutter create shopmart
cd shopmart
Step 2: Add Dependencies
Update pubspec.yaml:
pubspec.yaml
name: shopmart
description: A Flutter e-commerce application
version: 1.0.0+1
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
provider: ^6.1.1
cupertino_icons: ^1.0.6
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
flutter:
uses-material-design: true
Step 3: Install Dependencies
flutter pub get
3
Project Structure
Suggested Folder Layout
lib/
main.dart
models/
product.dart
cart_item.dart
order.dart
providers/
cart_provider.dart
data/
products_data.dart
screens/
home_screen.dart
product_detail_screen.dart
cart_screen.dart
checkout_screen.dart
order_confirmation_screen.dart
widgets/
product_card.dart
cart_item_widget.dart
category_chip.dart
utils/
constants.dart
4
Creating Product Model
lib/models/product.dart
class Product {
final String id;
final String name;
final String description;
final double price;
final String imageUrl;
final String category;
final int stock;
final double? rating;
final int? reviewCount;
Product({
required this.id,
required this.name,
required this.description,
required this.price,
required this.imageUrl,
required this.category,
required this.stock,
this.rating,
this.reviewCount,
});
String get formattedPrice => '\$${price.toStringAsFixed(2)}';
bool get inStock => stock > 0;
}
5
Creating Cart Item Model
lib/models/cart_item.dart
import 'product.dart';
class CartItem {
final Product product;
int quantity;
CartItem({
required this.product,
this.quantity = 1,
});
double get totalPrice => product.price * quantity;
String get formattedTotalPrice => '\$${totalPrice.toStringAsFixed(2)}';
CartItem copyWith({
Product? product,
int? quantity,
}) {
return CartItem(
product: product ?? this.product,
quantity: quantity ?? this.quantity,
);
}
}
6
Creating Order Model
lib/models/order.dart
import 'cart_item.dart';
class Order {
final String id;
final List items;
final double totalAmount;
final DateTime orderDate;
final String status;
final String? shippingAddress;
final String? paymentMethod;
Order({
required this.id,
required this.items,
required this.totalAmount,
required this.orderDate,
this.status = 'Pending',
this.shippingAddress,
this.paymentMethod,
});
String get formattedTotalAmount => '\$${totalAmount.toStringAsFixed(2)}';
int get totalItems => items.fold(0, (sum, item) => sum + item.quantity);
}
7
Creating Cart Provider
lib/providers/cart_provider.dart
import 'package:flutter/foundation.dart';
import '../models/product.dart';
import '../models/cart_item.dart';
class CartProvider with ChangeNotifier {
List _items = [];
List get items => _items;
int get itemCount => _items.fold(0, (sum, item) => sum + item.quantity);
double get totalAmount {
return _items.fold(0.0, (sum, item) => sum + item.totalPrice);
}
String get formattedTotalAmount => '\$${totalAmount.toStringAsFixed(2)}';
bool isInCart(Product product) {
return _items.any((item) => item.product.id == product.id);
}
int getQuantity(Product product) {
final item = _items.firstWhere(
(item) => item.product.id == product.id,
orElse: () => CartItem(product: product, quantity: 0),
);
return item.quantity;
}
void addItem(Product product) {
final existingIndex = _items.indexWhere(
(item) => item.product.id == product.id,
);
if (existingIndex >= 0) {
_items[existingIndex].quantity++;
} else {
_items.add(CartItem(product: product, quantity: 1));
}
notifyListeners();
}
void removeItem(Product product) {
final existingIndex = _items.indexWhere(
(item) => item.product.id == product.id,
);
if (existingIndex >= 0) {
if (_items[existingIndex].quantity > 1) {
_items[existingIndex].quantity--;
} else {
_items.removeAt(existingIndex);
}
notifyListeners();
}
}
void removeItemCompletely(Product product) {
_items.removeWhere((item) => item.product.id == product.id);
notifyListeners();
}
void updateQuantity(Product product, int quantity) {
if (quantity <= 0) {
removeItemCompletely(product);
return;
}
final existingIndex = _items.indexWhere(
(item) => item.product.id == product.id,
);
if (existingIndex >= 0) {
_items[existingIndex].quantity = quantity;
} else {
_items.add(CartItem(product: product, quantity: quantity));
}
notifyListeners();
}
void clearCart() {
_items.clear();
notifyListeners();
}
}
8
Creating Products Data
lib/data/products_data.dart
import '../models/product.dart';
class ProductsData {
static List getProducts() {
return [
Product(
id: '1',
name: 'Wireless Headphones',
description: 'High-quality wireless headphones with noise cancellation',
price: 99.99,
imageUrl: 'https://via.placeholder.com/300x300?text=Headphones',
category: 'Electronics',
stock: 50,
rating: 4.5,
reviewCount: 120,
),
Product(
id: '2',
name: 'Smart Watch',
description: 'Feature-rich smartwatch with fitness tracking',
price: 199.99,
imageUrl: 'https://via.placeholder.com/300x300?text=SmartWatch',
category: 'Electronics',
stock: 30,
rating: 4.7,
reviewCount: 89,
),
Product(
id: '3',
name: 'Running Shoes',
description: 'Comfortable running shoes for daily workouts',
price: 79.99,
imageUrl: 'https://via.placeholder.com/300x300?text=Shoes',
category: 'Fashion',
stock: 100,
rating: 4.3,
reviewCount: 200,
),
Product(
id: '4',
name: 'Laptop Backpack',
description: 'Durable backpack with laptop compartment',
price: 49.99,
imageUrl: 'https://via.placeholder.com/300x300?text=Backpack',
category: 'Fashion',
stock: 75,
rating: 4.6,
reviewCount: 150,
),
Product(
id: '5',
name: 'Coffee Maker',
description: 'Programmable coffee maker for your morning brew',
price: 89.99,
imageUrl: 'https://via.placeholder.com/300x300?text=CoffeeMaker',
category: 'Home',
stock: 40,
rating: 4.4,
reviewCount: 95,
),
Product(
id: '6',
name: 'Desk Lamp',
description: 'LED desk lamp with adjustable brightness',
price: 29.99,
imageUrl: 'https://via.placeholder.com/300x300?text=Lamp',
category: 'Home',
stock: 60,
rating: 4.2,
reviewCount: 78,
),
Product(
id: '7',
name: 'Yoga Mat',
description: 'Non-slip yoga mat for your fitness routine',
price: 24.99,
imageUrl: 'https://via.placeholder.com/300x300?text=YogaMat',
category: 'Sports',
stock: 80,
rating: 4.5,
reviewCount: 110,
),
Product(
id: '8',
name: 'Water Bottle',
description: 'Stainless steel insulated water bottle',
price: 19.99,
imageUrl: 'https://via.placeholder.com/300x300?text=Bottle',
category: 'Sports',
stock: 120,
rating: 4.7,
reviewCount: 250,
),
];
}
static List getCategories() {
return ['All', 'Electronics', 'Fashion', 'Home', 'Sports'];
}
}
9
Creating Product Card Widget
lib/widgets/product_card.dart
import 'package:flutter/material.dart';
import '../models/product.dart';
class ProductCard extends StatelessWidget {
final Product product;
final VoidCallback onTap;
const ProductCard({
Key? key,
required this.product,
required this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(8),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
child: Image.network(
product.imageUrl,
height: 150,
width: double.infinity,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
height: 150,
color: Colors.grey[300],
child: Icon(Icons.image_not_supported),
);
},
),
),
Padding(
padding: EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.name,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 4),
Text(
product.formattedPrice,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
if (product.rating != null) ...[
SizedBox(height: 4),
Row(
children: [
Icon(Icons.star, color: Colors.amber, size: 16),
SizedBox(width: 4),
Text(
'${product.rating}',
style: TextStyle(fontSize: 12),
),
if (product.reviewCount != null) ...[
SizedBox(width: 4),
Text(
'(${product.reviewCount})',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
],
],
),
],
SizedBox(height: 4),
Text(
product.inStock ? 'In Stock' : 'Out of Stock',
style: TextStyle(
fontSize: 12,
color: product.inStock ? Colors.green : Colors.red,
),
),
],
),
),
],
),
),
);
}
}
10
Creating Category Chip Widget
lib/widgets/category_chip.dart
import 'package:flutter/material.dart';
class CategoryChip extends StatelessWidget {
final String category;
final bool isSelected;
final VoidCallback onTap;
const CategoryChip({
Key? key,
required this.category,
required this.isSelected,
required this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return FilterChip(
label: Text(category),
selected: isSelected,
onSelected: (_) => onTap(),
selectedColor: Colors.blue[100],
checkmarkColor: Colors.blue,
labelStyle: TextStyle(
color: isSelected ? Colors.blue : Colors.black87,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
);
}
}
11
Creating Home Screen
lib/screens/home_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/product.dart';
import '../data/products_data.dart';
import '../providers/cart_provider.dart';
import '../widgets/product_card.dart';
import '../widgets/category_chip.dart';
import 'product_detail_screen.dart';
import 'cart_screen.dart';
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State {
String _selectedCategory = 'All';
String _searchQuery = '';
final TextEditingController _searchController = TextEditingController();
List get _filteredProducts {
var products = ProductsData.getProducts();
// Filter by category
if (_selectedCategory != 'All') {
products = products.where((p) => p.category == _selectedCategory).toList();
}
// Filter by search query
if (_searchQuery.isNotEmpty) {
products = products.where((p) {
return p.name.toLowerCase().contains(_searchQuery.toLowerCase()) ||
p.description.toLowerCase().contains(_searchQuery.toLowerCase());
}).toList();
}
return products;
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ShopMart'),
actions: [
Consumer(
builder: (context, cart, child) {
return Stack(
children: [
IconButton(
icon: Icon(Icons.shopping_cart),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => CartScreen()),
);
},
),
if (cart.itemCount > 0)
Positioned(
right: 8,
top: 8,
child: Container(
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
constraints: BoxConstraints(
minWidth: 16,
minHeight: 16,
),
child: Text(
'${cart.itemCount}',
style: TextStyle(
color: Colors.white,
fontSize: 10,
),
textAlign: TextAlign.center,
),
),
),
],
);
},
),
],
),
body: Column(
children: [
// Search bar
Padding(
padding: EdgeInsets.all(16),
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'Search products...',
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
onChanged: (value) {
setState(() {
_searchQuery = value;
});
},
),
),
// Category chips
Container(
height: 50,
child: ListView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(horizontal: 16),
children: ProductsData.getCategories().map((category) {
return Padding(
padding: EdgeInsets.only(right: 8),
child: CategoryChip(
category: category,
isSelected: _selectedCategory == category,
onTap: () {
setState(() {
_selectedCategory = category;
});
},
),
);
}).toList(),
),
),
SizedBox(height: 8),
// Products grid
Expanded(
child: _filteredProducts.isEmpty
? Center(
child: Text(
'No products found',
style: TextStyle(fontSize: 16, color: Colors.grey),
),
)
: GridView.builder(
padding: EdgeInsets.all(8),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.75,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: _filteredProducts.length,
itemBuilder: (context, index) {
final product = _filteredProducts[index];
return ProductCard(
product: product,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailScreen(product: product),
),
);
},
);
},
),
),
],
),
);
}
}
12
Creating Product Detail Screen
lib/screens/product_detail_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/product.dart';
import '../providers/cart_provider.dart';
class ProductDetailScreen extends StatelessWidget {
final Product product;
const ProductDetailScreen({Key? key, required this.product}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(product.name),
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Product image
Container(
height: 300,
width: double.infinity,
child: Image.network(
product.imageUrl,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
color: Colors.grey[300],
child: Icon(Icons.image_not_supported, size: 64),
);
},
),
),
Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Product name and price
Text(
product.name,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text(
product.formattedPrice,
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
SizedBox(height: 16),
// Rating
if (product.rating != null)
Row(
children: [
Icon(Icons.star, color: Colors.amber, size: 20),
SizedBox(width: 4),
Text(
'${product.rating}',
style: TextStyle(fontSize: 16),
),
if (product.reviewCount != null) ...[
SizedBox(width: 4),
Text(
'(${product.reviewCount} reviews)',
style: TextStyle(fontSize: 14, color: Colors.grey),
),
],
],
),
SizedBox(height: 16),
Divider(),
SizedBox(height: 16),
// Description
Text(
'Description',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text(
product.description,
style: TextStyle(fontSize: 14),
),
SizedBox(height: 16),
// Stock status
Row(
children: [
Icon(
product.inStock ? Icons.check_circle : Icons.cancel,
color: product.inStock ? Colors.green : Colors.red,
),
SizedBox(width: 8),
Text(
product.inStock
? 'In Stock (${product.stock} available)'
: 'Out of Stock',
style: TextStyle(
fontSize: 14,
color: product.inStock ? Colors.green : Colors.red,
),
),
],
),
SizedBox(height: 24),
// Add to cart button
Consumer(
builder: (context, cart, child) {
final isInCart = cart.isInCart(product);
final quantity = cart.getQuantity(product);
return Column(
children: [
if (isInCart) ...[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.remove_circle),
onPressed: () => cart.removeItem(product),
),
Text(
'$quantity',
style: TextStyle(fontSize: 20),
),
IconButton(
icon: Icon(Icons.add_circle),
onPressed: product.inStock
? () => cart.addItem(product)
: null,
),
],
),
SizedBox(height: 8),
],
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton.icon(
onPressed: product.inStock
? () {
cart.addItem(product);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('${product.name} added to cart'),
duration: Duration(seconds: 2),
),
);
}
: null,
icon: Icon(Icons.shopping_cart),
label: Text(
isInCart ? 'Add More to Cart' : 'Add to Cart',
style: TextStyle(fontSize: 18),
),
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
],
);
},
),
],
),
),
],
),
),
);
}
}
13
Creating Cart Item Widget
lib/widgets/cart_item_widget.dart
import 'package:flutter/material.dart';
import '../models/cart_item.dart';
class CartItemWidget extends StatelessWidget {
final CartItem item;
final VoidCallback onRemove;
final VoidCallback onDecrease;
final VoidCallback onIncrease;
const CartItemWidget({
Key? key,
required this.item,
required this.onRemove,
required this.onDecrease,
required this.onIncrease,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: ListTile(
leading: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network(
item.product.imageUrl,
width: 60,
height: 60,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
width: 60,
height: 60,
color: Colors.grey[300],
child: Icon(Icons.image_not_supported),
);
},
),
),
title: Text(
item.product.name,
style: TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.product.formattedPrice),
SizedBox(height: 4),
Row(
children: [
IconButton(
icon: Icon(Icons.remove_circle_outline),
onPressed: onDecrease,
iconSize: 20,
),
Text('${item.quantity}'),
IconButton(
icon: Icon(Icons.add_circle_outline),
onPressed: onIncrease,
iconSize: 20,
),
],
),
],
),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
item.formattedTotalPrice,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: onRemove,
),
],
),
),
);
}
}
14
Creating Cart Screen
lib/screens/cart_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/cart_provider.dart';
import '../widgets/cart_item_widget.dart';
import 'checkout_screen.dart';
class CartScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Shopping Cart'),
),
body: Consumer(
builder: (context, cart, child) {
if (cart.items.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.shopping_cart_outlined, size: 100, color: Colors.grey),
SizedBox(height: 16),
Text(
'Your cart is empty',
style: TextStyle(fontSize: 20, color: Colors.grey),
),
SizedBox(height: 8),
Text(
'Add some products to get started',
style: TextStyle(fontSize: 14, color: Colors.grey),
),
],
),
);
}
return Column(
children: [
Expanded(
child: ListView.builder(
itemCount: cart.items.length,
itemBuilder: (context, index) {
final item = cart.items[index];
return CartItemWidget(
item: item,
onRemove: () => cart.removeItemCompletely(item.product),
onDecrease: () => cart.removeItem(item.product),
onIncrease: () => cart.addItem(item.product),
);
},
),
),
// Total and checkout
Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, -3),
),
],
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Total:',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(
cart.formattedTotalAmount,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
],
),
SizedBox(height: 16),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CheckoutScreen(),
),
);
},
child: Text(
'Proceed to Checkout',
style: TextStyle(fontSize: 18),
),
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
],
),
),
],
);
},
),
);
}
}
15
Creating Checkout Screen
lib/screens/checkout_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/cart_provider.dart';
import '../models/order.dart';
import 'order_confirmation_screen.dart';
class CheckoutScreen extends StatefulWidget {
@override
_CheckoutScreenState createState() => _CheckoutScreenState();
}
class _CheckoutScreenState extends State {
final _formKey = GlobalKey();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _addressController = TextEditingController();
final _phoneController = TextEditingController();
String _selectedPaymentMethod = 'Credit Card';
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
_addressController.dispose();
_phoneController.dispose();
super.dispose();
}
void _placeOrder(BuildContext context, CartProvider cart) {
if (!_formKey.currentState!.validate()) return;
final order = Order(
id: DateTime.now().millisecondsSinceEpoch.toString(),
items: cart.items,
totalAmount: cart.totalAmount,
orderDate: DateTime.now(),
shippingAddress: _addressController.text,
paymentMethod: _selectedPaymentMethod,
);
cart.clearCart();
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => OrderConfirmationScreen(order: order),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Checkout'),
),
body: Consumer(
builder: (context, cart, child) {
return SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Shipping Information
Text(
'Shipping Information',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 16),
TextFormField(
controller: _nameController,
decoration: InputDecoration(
labelText: 'Full Name',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.person),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your name';
}
return null;
},
),
SizedBox(height: 16),
TextFormField(
controller: _emailController,
decoration: InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.email),
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
if (!value.contains('@')) {
return 'Please enter a valid email';
}
return null;
},
),
SizedBox(height: 16),
TextFormField(
controller: _phoneController,
decoration: InputDecoration(
labelText: 'Phone Number',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.phone),
),
keyboardType: TextInputType.phone,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your phone number';
}
return null;
},
),
SizedBox(height: 16),
TextFormField(
controller: _addressController,
decoration: InputDecoration(
labelText: 'Shipping Address',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.location_on),
),
maxLines: 3,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your address';
}
return null;
},
),
SizedBox(height: 32),
// Payment Method
Text(
'Payment Method',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 16),
...['Credit Card', 'Debit Card', 'PayPal', 'Cash on Delivery']
.map((method) {
return RadioListTile(
title: Text(method),
value: method,
groupValue: _selectedPaymentMethod,
onChanged: (value) {
setState(() {
_selectedPaymentMethod = value!;
});
},
);
}).toList(),
SizedBox(height: 32),
// Order Summary
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Order Summary',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Items (${cart.itemCount}):'),
Text(cart.formattedTotalAmount),
],
),
SizedBox(height: 8),
Divider(),
SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Total:',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
cart.formattedTotalAmount,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
],
),
],
),
),
),
SizedBox(height: 24),
// Place Order Button
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: () => _placeOrder(context, cart),
child: Text(
'Place Order',
style: TextStyle(fontSize: 18),
),
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
],
),
),
);
},
),
);
}
}
16
Creating Order Confirmation Screen
lib/screens/order_confirmation_screen.dart
import 'package:flutter/material.dart';
import '../models/order.dart';
import 'home_screen.dart';
class OrderConfirmationScreen extends StatelessWidget {
final Order order;
const OrderConfirmationScreen({Key? key, required this.order}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Order Confirmed'),
automaticallyImplyLeading: false,
),
body: Center(
child: Padding(
padding: EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.check_circle,
size: 100,
color: Colors.green,
),
SizedBox(height: 24),
Text(
'Order Placed Successfully!',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
SizedBox(height: 16),
Text(
'Thank you for your purchase',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
textAlign: TextAlign.center,
),
SizedBox(height: 32),
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
_buildInfoRow('Order ID', order.id),
Divider(),
_buildInfoRow('Total Items', '${order.totalItems}'),
Divider(),
_buildInfoRow('Total Amount', order.formattedTotalAmount),
Divider(),
_buildInfoRow('Payment Method', order.paymentMethod ?? 'N/A'),
Divider(),
_buildInfoRow('Status', order.status),
],
),
),
),
SizedBox(height: 32),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: () {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
(route) => false,
);
},
child: Text(
'Continue Shopping',
style: TextStyle(fontSize: 18),
),
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
],
),
),
),
);
}
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(fontSize: 14, color: Colors.grey[700]),
),
Text(
value,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
],
),
);
}
}
17
Updating Main.dart
lib/main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'providers/cart_provider.dart';
import 'screens/home_screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => CartProvider(),
child: MaterialApp(
title: 'ShopMart',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: HomeScreen(),
),
);
}
}
18
Project Checklist
Implementation Checklist
- ✅ Project created with Provider dependency
- ✅ Folder structure organized
- ✅ Product, CartItem, and Order models created
- ✅ CartProvider with state management
- ✅ Products data with sample products
- ✅ Product card widget
- ✅ Category chip widget
- ✅ Home screen with search and category filtering
- ✅ Product detail screen
- ✅ Cart item widget
- ✅ Cart screen with total calculation
- ✅ Checkout screen with form validation
- ✅ Order confirmation screen
- ✅ Main.dart with Provider setup
19
Enhancement Ideas
Optional Enhancements
- Add user authentication
- Implement product favorites/wishlist
- Add product reviews and ratings
- Implement order history
- Add product images from actual URLs
- Implement product search with filters
- Add shipping cost calculation
- Implement discount codes and coupons
- Add product recommendations
- Implement push notifications for orders
20
Exercises
1. Complete Implementation
Implement the entire e-commerce app following all the code provided. Test all features including product browsing, cart management, and checkout process.
2. Add Wishlist
Create a wishlist feature that allows users to save products for later. Add a heart icon to product cards and create a wishlist screen to view saved products.
3. Implement Order History
Create an order history screen that displays all past orders. Store orders locally or integrate with a backend service. Allow users to view order details.